﻿CBR-MP3Amp 0.6e
Technische Begleitdokumentation
================================

Plattform:
    Windows 10/11 x64

Assembler:
    Flat Assembler (FASM 1)

Quelltext:
    CBR-MP3Amp_0.6e.asm

Assemblieren:
    fasm CBR-MP3Amp_0.6e.asm CBR-MP3Amp_0.6e.exe


Vorwort
-------

Diese Datei ist kein Entwicklungs-Log und keine chronologische Liste alter
Fehler. Sie beschreibt den Aufbau von CBR-MP3Amp 0.6e so, wie dieser konkrete
Source funktioniert.

0.6e ist noch die MCI-Generation des Players. Der spätere eingebettete
nocash-MP3-Decoder ist hier NICHT enthalten. Auch Ordnerbrowser, Albumcover
und Hover-Cover gehören noch nicht zu dieser Version.

Der Name CBR-MP3Amp beschreibt den vorgesehenen Einsatz dieser Variante.
Der Source selbst analysiert die MP3-Datei jedoch nicht auf CBR oder VBR und
weist VBR-Dateien auch nicht ausdrücklich zurück. Er übergibt MP3-Dateien an
Windows MCI. Ob eine konkrete Datei funktioniert, hängt daher vom MCI-
Wiedergabepfad ab.


======================================================================
1. Grundidee dieser Version
======================================================================

CBR-MP3Amp 0.6e ist ein sehr kleiner portabler MP3-Player.

Beim Start wird der Ordner bestimmt, in dem sich die EXE befindet. Danach
werden dort alle Dateien gesucht, die auf:

    *.mp3

passen.

Diese Dateien erscheinen in einer Playlist. Es gibt keinen "Datei öffnen"-
Dialog und keine Musikbibliothek.

Das Bedienprinzip ist daher:

    EXE in einen MP3-Ordner legen -> EXE starten -> Titel auswählen.

Die Wiedergabe selbst übernimmt nicht eigener Decoder-Code, sondern Windows
MCI aus WINMM.DLL.


======================================================================
2. Native PE64-GUI
======================================================================

Der Source erzeugt eine echte 64-Bit-Windows-GUI:

    format PE64 GUI 5.0

Der Einstiegspunkt ist:

    start

Es gibt keine .NET-Laufzeit, kein Qt, kein SDL und kein anderes GUI-
Framework.

Das Fenster wird direkt mit klassischen Win32-Funktionen aufgebaut:

    GetModuleHandleW
    RegisterClassW
    CreateWindowExW
    ShowWindow
    UpdateWindow

Danach läuft die übliche Windows-Nachrichtenschleife:

    GetMessageW
    TranslateMessage
    DispatchMessageW

Das Programm beendet sich über:

    ExitProcess


======================================================================
3. Warum WndProc kein FASM-proc ist
======================================================================

Eine bemerkenswerte Stelle steht bereits im Kopf des Sources:

    WndProc wird absichtlich NICHT mit dem FASM-proc-Makro erzeugt.

WndProc ist ein roher Win64-Callback.

Beim Aufruf durch Windows gelten:

    RCX = HWND
    RDX = Nachricht
    R8  = WPARAM
    R9  = LPARAM

Der Source verzweigt direkt anhand der Nachricht auf lokale Zweige wie:

    .create
    .command
    .drawitem
    .hscroll
    .timer
    .mcinotify
    .destroy

Nicht selbst behandelte Nachrichten werden direkt an:

    DefWindowProcW

weitergereicht.

Das ist für einen Assembler-Source interessant, weil die tatsächliche
Microsoft-x64-Aufrufkonvention sehr sichtbar bleibt.


======================================================================
4. Eine kleine Win64-Besonderheit beim Programmstart
======================================================================

Am EntryPoint steht im Kommentar:

    RSP = 8 mod 16

Deshalb macht start zunächst:

    sub rsp, 8

Damit wird der Stack für die anschließende Benutzung von FASMs invoke-Makro
passend ausgerichtet.

Auch die manuell aufgerufenen Unterfunktionen reservieren jeweils den für den
Win64-Aufruf benötigten Stackbereich.

Bei handgeschriebenem x64-Assembler ist dieses Stack-Alignment wichtig, weil
Windows-APIs die Microsoft-x64-ABI erwarten.


======================================================================
5. WM_CREATE kommt früher, als man zunächst erwarten könnte
======================================================================

Eine besonders gute Lehrstelle ist der WM_CREATE-Pfad.

CreateWindowExW erzeugt das Hauptfenster. Noch bevor CreateWindowExW an
start zurückkehrt, kann Windows bereits WndProc mit WM_CREATE aufrufen.

Deshalb kann der Source nicht darauf warten, dass start erst nach
CreateWindowExW das zurückgegebene HWND in hMain schreibt.

Stattdessen macht WndProc bei WM_CREATE sofort:

    mov qword [hMain], rcx

RCX enthält zu diesem Zeitpunkt bereits das gültige HWND des Hauptfensters.

Erst danach wird OnCreate aufgerufen und die GUI aufgebaut.


======================================================================
6. Fensterstil und Größe
======================================================================

Das Hauptfenster benutzt:

    WS_CAPTION
    WS_SYSMENU
    WS_MINIMIZEBOX

zusammengefasst in:

    MAIN_STYLE

Es gibt damit bewusst keinen normalen Größenänderungsrahmen und keinen
Maximieren-Button.

Die bei CreateWindowExW angegebene Fenstergröße ist:

    550 x 585 Pixel

Das Layout ist fest und kompakt.


======================================================================
7. Die Oberfläche besteht nur aus Standard-Controls
======================================================================

Die GUI wird komplett zur Laufzeit in OnCreate erzeugt.

Verwendet werden Windows-Standardklassen:

    EDIT
    STATIC
    BUTTON
    LISTBOX
    SCROLLBAR

Es gibt keine Dialog-Ressource und keine .RC-Datei für das Layout.

Die Schrift ist:

    Segoe UI

und wird direkt mit CreateFontW erzeugt.

Das bedeutet: Auch die Schrift ist keine eingebettete Ressource.


======================================================================
8. Das Titelfeld ist technisch ein EDIT-Control
======================================================================

Das Feld ganz oben sieht wie ein schlichtes Status-/Titelfeld aus.

Technisch wird aber ein EDIT-Control erzeugt:

    classEdit

mit:

    WS_BORDER
    ES_READONLY
    ES_AUTOHSCROLL

Es ist also ein schreibgeschütztes einzeiliges Eingabefeld.

Dort werden je nach Zustand angezeigt:

    der ausgewählte Dateiname
    "Wiedergabe"
    "Pause"
    "Gestoppt"
    ein MCI-Fehlertext

Das Feld ist 510 Pixel breit und 34 Pixel hoch.


======================================================================
9. Position und Zeit
======================================================================

Unter dem Titelfeld befinden sich:

    ein STATIC-Control für die Zeit
    eine horizontale SCROLLBAR für die Position

Die Zeit startet als:

    00:00 / 00:00

Der Positionsregler arbeitet nicht in Prozent, sondern im Bereich:

    0 .. 1000

Das entspricht praktisch Promille und gibt beim Spulen eine feinere
Auflösung als 0..100.


======================================================================
10. Wie die Transportknöpfe gezeichnet werden
======================================================================

Die fünf Knöpfe:

    Zurück
    Play
    Pause
    Stop
    Weiter

sind normale Windows-BUTTON-Controls.

Sie werden jedoch mit:

    BS_OWNERDRAW

erzeugt.

Dadurch zeichnet Windows den eigentlichen Inhalt nicht selbst, sondern sendet
WM_DRAWITEM an das Hauptfenster.

WndProc ruft dann:

    DrawTransportButton

auf.

Diese Funktion erhält einen DRAWITEMSTRUCT-Zeiger und liest daraus unter
anderem:

    CtlID
    HDC
    Rechteck des Buttons

Damit weiß sie, welcher Knopf gerade gezeichnet werden soll.


======================================================================
11. Hintergrund und Rahmen der Knöpfe
======================================================================

Der Hintergrund wird mit dem normalen Windows-Buttonfarbton gefüllt:

    GetSysColorBrush(COLOR_BTNFACE)
    FillRect

Der 1-Pixel-Rahmen entsteht mit:

    GetSysColorBrush(COLOR_3DSHADOW)
    FrameRect

Es werden also keine Bitmap-Rahmen und keine vorgefertigten Buttonbilder
benutzt.


======================================================================
12. Die Symbole sind echte GDI-Geometrie
======================================================================

Auch die Transport-Symbole sind keine PNGs, Bitmaps oder Icon-Ressourcen.

Der Source zeichnet sie geometrisch.

Play:
    ein Dreieck mit Polygon

Pause:
    zwei gefüllte Rechtecke mit FillRect

Stop:
    ein gefülltes Quadrat mit FillRect

Zurück:
    ein schmaler Balken plus links zeigendes Dreieck

Weiter:
    ein rechts zeigendes Dreieck plus schmaler Balken

Als Farbe wird ein schwarzer Systempinsel benutzt:

    GetStockObject(BLACK_BRUSH)

Das ist eine sehr kompakte Lösung: Die gesamte Grafik der Transportsteuerung
besteht aus ein paar Koordinaten und GDI-Aufrufen.


======================================================================
13. Text und Symbol werden getrennt gezeichnet
======================================================================

Das Symbol sitzt links im Button.

Rechts davon wird ein eigenes Textrechteck aufgebaut. Anschließend zeichnet:

    DrawTextW

die Beschriftung zentriert und vertikal mittig.

Verwendete Flags:

    DT_CENTER
    DT_VCENTER
    DT_SINGLELINE

Der Hintergrundmodus wird mit:

    SetBkMode(..., TRANSPARENT)

transparent gesetzt.

Dadurch besteht jeder Transportbutton aus:

    GDI-Symbol links
    normaler Segoe-UI-Text rechts


======================================================================
14. Kein eigener About-Dialog
======================================================================

Diese 0.6e-Version hat noch gar keinen About-Button und keinen About-Dialog.

Es existiert:

    keine About-WndProc
    kein About-Fenster
    keine About-Dialogressource

MessageBoxW wird in diesem Source ausschließlich für Startfehler verwendet,
beispielsweise wenn RegisterClassW oder CreateWindowExW fehlschlägt.

Das unterscheidet 0.6e von späteren Playerständen.


======================================================================
15. Die Playlist ist eine normale LISTBOX
======================================================================

Die Playlist wird als LISTBOX erzeugt.

Wichtige Styles:

    LBS_NOTIFY
    LBS_SORT
    WS_BORDER
    WS_VSCROLL

LBS_SORT bedeutet, dass Windows die eingetragenen Dateinamen automatisch
sortiert.

Der Player muss dafür keinen eigenen Sortieralgorithmus enthalten.


======================================================================
16. Wie der EXE-Ordner ermittelt wird
======================================================================

GetExeDirectory ruft zuerst:

    GetModuleFileNameW

auf.

Dadurch erhält der Player seinen eigenen vollständigen EXE-Pfad.

Danach läuft der Source rückwärts durch den UTF-16-String, bis der letzte:

    \

gefunden wird.

An dieser Stelle wird eine Null geschrieben.

Aus beispielsweise:

    C:\Music\CBR-MP3Amp_0.6e.exe

wird so:

    C:\Music

Dieser Ordner wird anschließend für die Playlist benutzt.


======================================================================
17. Wie die MP3-Dateien gefunden werden
======================================================================

LoadPlaylist baut aus dem EXE-Ordner und:

    \*.mp3

einen Suchpfad.

Die Dateisuche erfolgt mit:

    FindFirstFileW
    FindNextFileW
    FindClose

Einträge mit:

    FILE_ATTRIBUTE_DIRECTORY

werden übersprungen.

Jede gefundene Datei wird per:

    LB_ADDSTRING

in die ListBox eingetragen.

Wenn keine MP3-Datei gefunden wurde, erscheint im Titelfeld:

    Keine MP3-Dateien im CBR-MP3Amp-Ordner gefunden.


======================================================================
18. Warum Unicode-Dateinamen funktionieren
======================================================================

Der Source benutzt konsequent die Wide-/Unicode-Varianten der Windows-APIs:

    GetModuleFileNameW
    FindFirstFileW
    FindNextFileW
    CreateWindowExW
    SetWindowTextW
    mciSendStringW

Dateipuffer sind UTF-16-Puffer und die statischen Strings werden mit:

    du

definiert.

Dadurch ist der Player nicht auf die alte ANSI-Codepage beschränkt.


======================================================================
19. Einfacher Klick und Doppelklick haben verschiedene Aufgaben
======================================================================

Die ListBox sendet Benachrichtigungen über WM_COMMAND.

Bei:

    LBN_SELCHANGE

ruft der Source:

    ShowSelectedName

auf.

Das ändert nur den Text oben auf den ausgewählten Dateinamen.

Bei:

    LBN_DBLCLK

wird dagegen:

    PlaySelected

aufgerufen.

Ein einfacher Klick wählt also nur aus, ein Doppelklick startet tatsächlich
die Wiedergabe.


======================================================================
20. MCI ist das komplette Audio-Backend
======================================================================

CBR-MP3Amp 0.6e enthält keinen eigenen MP3-Decoder.

Die Audiofunktion kommt aus:

    WINMM.DLL

Verwendet werden nur zwei MCI-Funktionen:

    mciSendStringW
    mciGetErrorStringW

Fast die gesamte Wiedergabelogik besteht aus Textkommandos, die an MCI
geschickt werden.

Das macht diesen Source besonders anschaulich: Man kann die Audiooperationen
nahezu als Klartext lesen.


======================================================================
21. Der MCI-Alias "song"
======================================================================

Beim Start eines Titels wird zuerst der vollständige Pfad aufgebaut.

Dann entsteht das Kommando:

    open "<vollständiger Pfad>" type mpegvideo alias song

Der wichtige Teil ist:

    alias song

Nach erfolgreichem Öffnen muss der Source nicht mehr ständig den Dateipfad
angeben.

Alle weiteren Befehle sprechen einfach das MCI-Gerät mit dem Namen:

    song

an.


======================================================================
22. Die wichtigsten MCI-Kommandos
======================================================================

Im Datenbereich stehen die Befehle als UTF-16-Strings.

Öffnen:
    open "..." type mpegvideo alias song

Zeitformat:
    set song time format milliseconds

Länge:
    status song length

Aktuelle Position:
    status song position

Abspielen:
    play song

Abspielen mit Benachrichtigung:
    play song notify

Pause:
    pause song

Fortsetzen:
    resume song

Stop:
    stop song

Schließen:
    close song

Lautstärke:
    setaudio song volume to ...

Seek:
    seek song to ...

Diese Strings sind der Kern des MCI-Backends.


======================================================================
23. Warum "type mpegvideo" bei einem MP3-Player steht
======================================================================

Im Open-Kommando steht ausdrücklich:

    type mpegvideo

obwohl der Player MP3-Dateien öffnet.

Das ist kein eigener Videopfad im Programm. Es ist lediglich der in diesem
MCI-Aufruf verwendete Gerätetyp.

CBR-MP3Amp selbst verarbeitet keinerlei Videodaten.


======================================================================
24. Ablauf von PlaySelected
======================================================================

PlaySelected macht im Wesentlichen Folgendes:

1. aktuelle ListBox-Auswahl holen
2. Dateinamen auslesen
3. vollständigen Pfad aus EXE-Ordner + "\" + Dateiname bilden
4. eventuell vorher geöffnetes MCI-"song" schließen
5. neues "song" mit mciSendStringW öffnen
6. Zeitformat auf Millisekunden setzen
7. Tracklänge abfragen
8. aktuelle Lautstärke setzen
9. "play song notify" starten
10. Dateinamen im Titelfeld anzeigen

Scheitert das Öffnen oder Play, wird ShowMciError benutzt.


======================================================================
25. Warum die Lautstärke vor Play gesetzt wird
======================================================================

Im Source steht ausdrücklich:

    Erst Lautstaerke setzen, DANACH play.

Das verhindert, dass ein neu gestarteter Titel für einen kurzen Moment mit
einer unerwünschten Standardlautstärke beginnt.

Der Startwert des Players beträgt:

    30 Prozent


======================================================================
26. Lautstärkeregelung
======================================================================

Der sichtbare Regler arbeitet von:

    0 .. 100

MCI erwartet für dieses Kommando:

    0 .. 1000

ApplyVolume multipliziert deshalb:

    volumePercent * 10

und wandelt das Ergebnis mit UIntToDecW in einen UTF-16-Dezimalstring um.

Danach wird beispielsweise aufgebaut:

    setaudio song volume to 300

für 30 Prozent.


======================================================================
27. Die sichtbare Lautstärkeanzeige wird selbst zusammengesetzt
======================================================================

UpdateVolumeLabel erzeugt den Text:

    Lautstaerke: NN%

nicht mit printf oder einer C-Laufzeitbibliothek.

Stattdessen werden:

    lstrcpyW
    UIntToDecW
    lstrcatW

verwendet.

Auch das ist typisch für den gesamten Source: Es gibt keine C-Runtime.


======================================================================
28. Beide Regler kommen über dieselbe Windows-Nachricht
======================================================================

Positions- und Lautstärkeregler sind beide SCROLLBAR-Controls.

Beide senden deshalb:

    WM_HSCROLL

WndProc übergibt WPARAM und LPARAM an:

    OnHScroll

OnHScroll schaut zuerst auf LPARAM und vergleicht das sendende HWND mit:

    hPosition
    hVolume

So kann derselbe Nachrichtenhandler beide Regler auseinanderhalten.


======================================================================
29. Ein besonders wichtiger Sprung nach der Lautstärkeänderung
======================================================================

Nach dem Anwenden einer Lautstärkeänderung steht im Source ausdrücklich:

    jmp .done

Der Kommentar erklärt auch warum.

Ohne diesen Sprung könnte die Ausführung in den direkt folgenden
.position_control-Code hineinfallen.

Dann würde dieselbe WM_HSCROLL-Nachricht zusätzlich als Spulbefehl
interpretiert.

Das ist eine kleine Kontrollflussstelle mit großer Auswirkung.


======================================================================
30. Schrittweiten des Lautstärkereglers
======================================================================

OnHScroll behandelt verschiedene Scrollbar-Codes.

Für Lautstärke:

    LINELEFT     -2
    LINERIGHT    +2
    PAGELEFT     -10
    PAGERIGHT    +10
    LEFT         0
    RIGHT        100

ThumbTrack und ThumbPosition übernehmen direkt die Position des Schiebers.

Danach wird immer auf:

    0 .. 100

begrenzt.


======================================================================
31. Die Tracklänge kommt als Text von MCI
======================================================================

Nach dem Öffnen wird:

    status song length

ausgeführt.

MCI schreibt das Ergebnis als UTF-16-Dezimaltext in:

    statusBuffer

ParseUIntW liest die Ziffern Zeichen für Zeichen und baut daraus eine
32-Bit-Zahl.

Da vorher:

    set song time format milliseconds

gesetzt wurde, ist trackLength anschließend eine Millisekundenzahl.


======================================================================
32. Alle 500 ms wird die Position aktualisiert
======================================================================

OnCreate startet:

    SetTimer(..., 500, ...)

Der Timer erzeugt alle 500 Millisekunden WM_TIMER.

WndProc ruft dann:

    UpdatePosition

auf.

UpdatePosition fragt MCI mit:

    status song position

nach der aktuellen Millisekundenposition.

Danach wird sowohl der Positionsregler als auch die Zeitanzeige aktualisiert.


======================================================================
33. Umrechnung auf den Positionsregler
======================================================================

Der Regler arbeitet von 0 bis 1000.

UpdatePosition berechnet deshalb:

    currentPosition * 1000 / trackLength

und setzt diesen Wert mit:

    SetScrollPos

Damit entspricht:

    0       Trackanfang
    500     ungefähr Trackmitte
    1000    Trackende


======================================================================
34. Zeitformat MM:SS / MM:SS
======================================================================

UpdateTimeText wandelt aktuelle Position und Gesamtlänge von Millisekunden
in Sekunden um.

WriteMMSS erzeugt daraus jeweils:

    MM:SS

Zusammengesetzt entsteht:

    MM:SS / MM:SS

Beispiel:

    02:17 / 04:53

Es wird keine Formatierungsbibliothek verwendet. Jede Ziffer wird im
Assembler selbst berechnet und als UTF-16-Zeichen in den Puffer geschrieben.


======================================================================
35. Eine kleine Anzeigegrenze: 99 Minuten
======================================================================

WriteMMSS enthält:

    cmp eax, 99
    ...
    mov eax, 99

Die Minutenanzeige wird damit bewusst auf 99 begrenzt.

Sehr lange Audiodateien können also intern eine größere Millisekundenlänge
haben, die sichtbare MM:SS-Anzeige bleibt aber bei maximal 99 Minuten.


======================================================================
36. Seek in dieser Version
======================================================================

Zum Spulen wird aus:

    Tracklänge
    Reglerposition 0..1000

eine Zielposition in Millisekunden berechnet.

Danach baut SeekToTarget:

    seek song to <Millisekunden>

und schickt dieses Kommando an MCI.


======================================================================
37. Seek erfolgt bereits während THUMBTRACK
======================================================================

Diese 0.6e-Version trennt Vorschau und endgültigen Seek noch nicht.

Sowohl:

    SB_THUMBTRACK
als auch
    SB_THUMBPOSITION

gehen direkt in den Seek-Pfad.

Das bedeutet: Während der Benutzer den Schieber zieht, kann MCI wiederholt
Seek-Kommandos bekommen.

Das ist ein Unterschied zu späteren Playerständen, bei denen das eigentliche
Spulen erst beim Loslassen ausgelöst wurde.


======================================================================
38. Pausezustand beim Seek
======================================================================

Ein MCI-Seek stoppt die aktuelle Wiedergabe.

Wenn vorher nicht pausiert war, startet SeekToTarget anschließend wieder:

    play song notify

War der Titel dagegen pausiert, führt der Source kurz:

    play song
    pause song

aus.

Damit bleibt der logische Pausezustand nach dem Positionssprung erhalten.


======================================================================
39. Eine technische Grenze der Seek-Multiplikation
======================================================================

Im Seek-Pfad wird zunächst:

    trackLength * newPosition

mit mul berechnet.

mul liefert bei 32-Bit-Operanden eigentlich ein 64-Bit-Ergebnis in EDX:EAX.

Unmittelbar vor der Division wird EDX in dieser Version jedoch wieder auf
Null gesetzt.

Damit wird effektiv nur der untere 32-Bit-Teil des Produkts verwendet.

Bei normalen Musiktiteln fällt das üblicherweise nicht auf. Bei sehr langen
Dateien kann die Multiplikation jedoch über 32 Bit hinauslaufen und der
berechnete Seek-Wert wäre dann nicht mehr korrekt.

Das ist eine echte technische Eigenschaft des 0.6e-Sources und keine
CBR/VBR-Frage.


======================================================================
40. Play und Resume
======================================================================

Der Play-Button hat zwei Aufgaben.

Wenn:

    isPaused = 1

versucht OnCommand zuerst:

    resume song

Nur wenn das nicht klappt bzw. kein Pausezustand aktiv ist, wird
PlaySelected aufgerufen.

Damit ist derselbe Button sowohl "neuen ausgewählten Titel starten" als auch
"aus Pause fortsetzen".


======================================================================
41. Stop
======================================================================

Stop sendet:

    stop song

setzt:

    isPaused = 0

und zeigt:

    Gestoppt

im Titelfeld.

Das MCI-Gerät bleibt dabei zunächst geöffnet. Es wird beim nächsten
PlaySelected oder beim Programmende mit:

    close song

geschlossen.


======================================================================
42. Previous und Next sind zyklisch
======================================================================

NextTrack liest:

    LB_GETCOUNT
    LB_GETCURSEL

und erhöht die Auswahl.

Nach dem letzten Titel springt die Auswahl wieder auf:

    0

PreviousTrack macht das Gegenstück.

Beim ersten Titel springt Previous auf den letzten Eintrag.

Nach der Auswahl ruft beides direkt:

    PlaySelected

auf.


======================================================================
43. Auto-Next ohne eigenen Audiothread
======================================================================

Der Source benötigt für Auto-Next keinen Decoderthread.

Der Titel wird mit:

    play song notify

gestartet.

Wenn MCI die Wiedergabe erfolgreich beendet hat, sendet Windows:

    WM_MCINOTIFY

an das Hauptfenster.

WndProc ruft:

    OnMciNotify

auf.

Nur wenn WPARAM:

    MCI_NOTIFY_SUCCESSFUL

meldet, wird:

    NextTrack

gestartet.

Auto-Next ist in dieser Version also vollständig ereignisbasiert über MCI.


======================================================================
44. Fehlertexte von MCI
======================================================================

mciSendStringW liefert bei Fehlern einen MCI-Fehlercode.

ShowMciError versucht diesen Code mit:

    mciGetErrorStringW

in verständlichen Text umzuwandeln.

Gelingt das, wird der Windows-MCI-Fehlertext direkt im Titelfeld angezeigt.

Gelingt es nicht, wird der allgemeine Text:

    MP3 konnte nicht abgespielt werden.

verwendet.

Dafür ist kein zusätzlicher Fehlerdialog nötig.


======================================================================
45. Startfehler benutzen MessageBoxW
======================================================================

Während normale MCI-Wiedergabefehler oben im Player erscheinen, werden
schwere Fehler beim Programmstart mit MessageBoxW gemeldet.

Es gibt eigene Texte für:

    GetModuleHandleW fehlgeschlagen
    RegisterClassW fehlgeschlagen
    CreateWindowExW fehlgeschlagen
    GetMessageW fehlgeschlagen

Der Caption-Text lautet:

    CBR-MP3Amp 0.6e - Startfehler

Diese MessageBoxen sind keine About-Dialoge, sondern nur Fehleranzeigen.


======================================================================
46. Sauberes Beenden
======================================================================

Bei:

    WM_DESTROY

passieren drei wesentliche Dinge.

Zuerst wird der 500-ms-Timer gestoppt:

    KillTimer

Dann wird MCI geschlossen:

    close song

Danach wird die mit CreateFontW erzeugte Schrift mit:

    DeleteObject

freigegeben.

Zum Schluss:

    PostQuitMessage

Dadurch verlässt GetMessageW die Nachrichtenschleife und start endet über
ExitProcess.


======================================================================
47. Keine eigenen Threads
======================================================================

Anders als spätere Decoderstände besitzt 0.6e keinen Audioworker-Thread.

Es gibt keinen:

    CreateThread

für die Wiedergabe und keinen eigenen PCM-Pufferkreislauf.

MCI kapselt die eigentliche Audioarbeit innerhalb des Windows-
Multimediasystems.

Die GUI kommuniziert nur über MCI-Kommandos und Windows-Nachrichten.


======================================================================
48. Kein direkter waveOut-Code
======================================================================

Der Source importiert keine Funktionen wie:

    waveOutOpen
    waveOutWrite
    waveOutPause
    waveOutReset

Stattdessen wird ausschließlich die höhere MCI-Ebene benutzt.

Das ist einer der größten architektonischen Unterschiede zu späteren
Versionen mit eingebettetem MP3-Decoder.


======================================================================
49. Kein eigener MP3-Parser
======================================================================

CBR-MP3Amp 0.6e liest keine MPEG-Frames.

Es analysiert nicht:

    MPEG-Version
    Layer
    Bitrate
    Samplerate
    Xing-Header
    LAME-Header
    Bit Reservoir
    ID3-Audio-Metadaten

Die einzige eigene Dateilogik besteht im Finden der *.mp3-Dateien und im
Zusammensetzen des vollständigen Pfads.

Alles Audioformatbezogene wird an MCI delegiert.


======================================================================
50. "CBR" ist ein Name, keine Dateiprüfung
======================================================================

Wichtig für jemanden, der den Source liest:

Es gibt keine Funktion wie:

    IsCBR
    CheckBitrate
    ParseMP3Header

Der Player prüft also nicht, ob eine Datei wirklich konstante Bitrate hat.

Der Name CBR-MP3Amp beschreibt die Zielsetzung bzw. den als zuverlässig
vorgesehenen MP3-Typ dieser MCI-Version.

Technisch versucht der Source jede Datei abzuspielen, die in der Playlist als
*.mp3 auftaucht.


======================================================================
51. Welche DLLs verwendet werden
======================================================================

Der Source importiert vier Windows-Systembibliotheken:

KERNEL32.DLL
    Modulpfad, Dateisuche, Stringhilfen, Prozessende

USER32.DLL
    Fenster, Nachrichtenschleife, Controls, Scrollbars, Timer, Zeichnen

GDI32.DLL
    Font und geometrische Button-Symbole

WINMM.DLL
    MCI-Wiedergabe und MCI-Fehlertexte

Es gibt keine zusätzliche mitzuliefernde Programmbibliothek.


======================================================================
52. Keine externen Grafikressourcen
======================================================================

Die Oberfläche benötigt:

    keine BMP-Datei
    keine PNG-Datei
    keine eingebetteten Buttonbilder

Die Transport-Symbole werden mit GDI erzeugt.

Dadurch kann die Anwendung als einzelne EXE verteilt werden.


======================================================================
53. Wichtige Datenfelder
======================================================================

Für den Zustand der Wiedergabe gibt es nur wenige zentrale Variablen.

isOpen
    MCI-"song" ist geöffnet.

isPaused
    logischer Pausezustand.

volumePercent
    Lautstärke 0..100, Startwert 30.

trackLength
    Tracklänge in Millisekunden.

currentPosition
    aktuelle MCI-Position in Millisekunden.

seekTarget
    Ziel eines Seek-Vorgangs in Millisekunden.

currentIndex
    ausgewählter Playlistindex.

trackCount
    Anzahl der Playlisttitel.


======================================================================
54. Warum es relativ große Unicode-Puffer gibt
======================================================================

Der Source reserviert feste Puffer für:

    exeDir
    searchPath
    fileName
    fullPath
    commandBuffer
    errorBuffer
    statusBuffer
    timeBuffer

commandBuffer ist besonders groß, weil dort komplette MCI-Kommandos inklusive
vollständigem Unicode-Dateipfad zusammengesetzt werden.

Es gibt in dieser Version noch keine separate .bss-Aufteilung wie in späteren
Cleanup-Ständen; die Arbeitsbereiche befinden sich in der .data-Section.


======================================================================
55. Eigene Integer-zu-Text-Konvertierung
======================================================================

UIntToDecW wandelt eine unsigned 32-Bit-Zahl ohne C-Laufzeitbibliothek in
einen UTF-16-Dezimalstring um.

Die Routine teilt wiederholt durch 10, schreibt Ziffern rückwärts in einen
kleinen temporären Puffer und kopiert sie anschließend vorwärts ins Ziel.

Sie wird unter anderem benutzt für:

    Lautstärkekommandos
    Seek-Kommandos
    sichtbare Lautstärkeanzeige


======================================================================
56. ParseUIntW ist das Gegenstück
======================================================================

ParseUIntW liest einen UTF-16-Dezimaltext aus statusBuffer.

Für jede Ziffer macht die Routine sinngemäß:

    wert = wert * 10 + ziffer

Sobald ein Zeichen außerhalb '0' bis '9' erscheint, endet die Konvertierung.

Damit kann der Source MCI-Statusantworten ohne scanf, atoi oder CRT
verarbeiten.


======================================================================
57. Source-Stellen, die sich gut zum Lernen eignen
======================================================================

Für jemanden, der den ASM-Source lesen möchte, ist folgende Reihenfolge
sinnvoll:

    start
        Fensterklasse, Hauptfenster, Message Loop

    WndProc
        rohe Win64-Callback-Struktur

    OnCreate
        komplette Oberfläche

    DrawTransportButton
        Owner-Draw und GDI-Geometrie

    OnCommand
        Buttons und Playlistbenachrichtigungen

    LoadPlaylist
        FindFirstFileW/FindNextFileW

    GetExeDirectory
        eigener EXE-Ordner

    PlaySelected
        kompletter MCI-Trackstart

    OnHScroll
        Lautstärke und Seek

    ApplyVolume
        MCI-Lautstärkekommando

    UpdatePosition
        Timer und MCI-Positionsabfrage

    SeekToTarget
        MCI-Seek und Pausebehandlung

    OnMciNotify
        Auto-Next

    UIntToDecW / ParseUIntW
        Zahlenkonvertierung ohne Runtime


======================================================================
58. Bereiche, bei Änderungen besonders vorsichtig behandeln
======================================================================

Win64-Stack:
    Das Alignment und die reservierten Stackbereiche nicht leichtfertig
    verändern.

WndProc:
    Die Registerbelegung RCX/RDX/R8/R9 ist Teil der Windows-x64-ABI.

WM_CREATE:
    hMain muss früh genug gesetzt sein, weil WM_CREATE innerhalb von
    CreateWindowExW eintrifft.

WM_HSCROLL:
    Lautstärke- und Positionsregler teilen sich dieselbe Nachricht.
    Nach Lautstärke nicht in den Positionsteil durchfallen.

MCI-Alias:
    Alle Kommandos setzen voraus, dass das geöffnete Gerät "song" heißt.

Play mit Notify:
    Auto-Next hängt an "play song notify" und WM_MCINOTIFY.

Unicode:
    Die W-APIs und UTF-16-Puffer sollten nicht halb auf ANSI umgestellt
    werden.

Seek:
    Die 32-Bit-Multiplikation ist bei extrem langen Dateien eine bekannte
    technische Grenze dieses Standes.


======================================================================
59. Was diese Version bewusst NICHT enthält
======================================================================

Nicht Bestandteil von CBR-MP3Amp 0.6e sind:

    eingebetteter eigener MP3-Decoder
    waveOut-Pufferverwaltung
    CBR/VBR-Headeranalyse
    Xing-/LAME-Seek
    Layer-III-Pre-Roll
    Ordnerbrowser
    Laufwerksauswahl
    Albumcover
    ID3v2-PIC/APIC-Auswertung
    GDI+-Bildanzeige
    400x400-Hover-Cover
    About-Button
    Equalizer
    Visualisierung
    Medienbibliothek
    externe Grafiken

Gerade dadurch ist 0.6e ein überschaubarer Source, um eine klassische
Win64-GUI mit MCI zu studieren.


======================================================================
60. Kurz zusammengefasst
======================================================================

CBR-MP3Amp 0.6e kombiniert nur wenige Bausteine:

    Win32-Standardfenster
    rohe Win64-WndProc
    Standard-Controls
    Owner-Draw-Transportbuttons
    GDI-Geometrie
    Unicode-Dateisuche
    ListBox-Playlist
    zwei Scrollbars
    500-ms-Timer
    Windows MCI

Der Player decodiert MP3 nicht selbst. Stattdessen baut er MCI-
Textkommandos und lässt WINMM die Wiedergabe übernehmen.

Gerade diese Einfachheit macht die Version technisch interessant:
Die komplette sichtbare Oberfläche, Playlistlogik, Zeitdarstellung,
Lautstärkeregelung, Seek-Funktion und Auto-Next-Steuerung sind in einem
vergleichsweise kleinen FASM-Source direkt nachvollziehbar.


======================================================================
Ende
======================================================================
